2

属性描述符给js添加了巨大的可能性,借此文章留个记录.

对象属性

Object.getOwnPropertyDescriptor()

此函数传入两个参数,一个是目标对象,一个是目标对象的属性,返回的是一个该属性的属性描述符对象,就像这样。

var obj = {test: 'test'}
console.log(Object.getOwnPropertyDescriptor(obj, 'test'))
// 输出
{ value: 'test',
  writable: true,
  enumerable: true,
  configurable: true 
}

可以看到,如果是用字面量声明的对象,默认 writable enumerable configurable 的值都是true

Object.defineProperty()

此函数传入3葛参数,第一个是目标对象,第二个是要修改的目标对象的属性(可以是目标对象没有的属性),第三个是属性描述符对象,

wratable 属性描述符

Object.defineProperty(myObject, 'test', {

    writable: false
})

myObject.a = 'test again'

console.log(myObject)// {test: 'test'} ???

我的again呢?

在writable 为false 的时候,js会忽略掉该属性的赋值行为,(若想它告诉你这不可行,请使用严格模式)

下面尝试define 一个不存在的属性的property,就像这样

Object.defineProperty(myObject, 'test2', {
    value: 'testtest'
})
console.log(Object.getOwnPropertyDescriptor(myObject, 'test2'))
// 输出
{ value: 'testtest',
  writable: false,
  enumerable: false,
  configurable: false 
}

定义一个不存在的属性会创建一个只有value, 其他描述符都为false的属性,这样定义的属性,其他描述符默认为false(超级安全)

现在来试试 修改configurable吧

Object.defineProperty(myObject, 'test', {
    configurable: false,
    value: 'testconfigurable',
    enumerable: true,
    writable: false
})

Object.defineProperty(myObject, 'test', {
    writable: true
})

myObject.test = 'config writable after set the configurable flase';
console.log(Object.getOwnPropertyDescriptor(myObject, 'test'))
console.log(myObject.test) // error: cant not redefine.....

噢 报错了,我们把configurable 修改的同时 把 wriable 也弄成false了,然后 再把writable 给弄回来ture 发现报错了,原来 configurable 是不让我们修改 test 这个属性的描述符了吗?

先修改configurable 再动 writable。

Object.defineProperty(myObject, 'test', {
    configurable: false,
    value: 'testconfigurable',
    enumerable: true,
    writable: true
})

Object.defineProperty(myObject, 'test', {
    writable: false
})

myObject.test = 'config writable after set the configurable flase';
console.log(Object.getOwnPropertyDescriptor(myObject, 'test'))
console.log(myObject.test) // testconfigurable

赋值失败??? writable false 生效? 什么情况。。。(不知道是bug,还是另有原因,不过谁让他是js,就是这么神奇)

不修改了, 直接移除,就像这样

var myObject = {test: 'test'}
console.log(myObject) // test
delete myObject.test
console.log(myObject.test) // undefined
// myObject.a = 'bbb'

myObject.test = 'test';
Object.defineProperty(myObject, 'test', {
    configurable: false,
    value: 'testconfigurable',
    enumerable: true,
    writable: true
})
console.log(myObject) // testconfigurable
delete myObject.test
console.log(myObject) // testconfigurable

可以看到,删除也是无效的.

enumerable (可枚举的)

代码先行

var myObject = {test: 'test'}

Object.defineProperty(myObject, 'test', {
    configurable: false,
    value: 'testconfigurable',
    enumerable: false,
    writable: true
})

console.log(myObject)
// 输出
{ value: 'testconfigurable',
  writable: true,
  enumerable: false,
  configurable: false }

{}

enumerable 是控制属性是否能 照常显示的描述符,若为false 不单只console.log 不出来 就连 forin 循环也不会遍历到他的key值

好了,所有的属性描述符的性质都已经介绍过了,我们已经知道了他们是什么了,但是这些描述符有什么用呢?为什么需要这些描述符呢?

创建不可变的对象

试想一下,在没有属性描述符的时候,如何解决对象这种引用类型的不可变?不可能。。。

上面我们已经说过,当一个属性被设置为configurable 为 false 的时候,该属性不能被删除,当我们设置writable 为false 的时候,该属性不能被修改,这好像就是不可变,可上文演示的是字符串,现在我们来测试一下对象

var myObject = {test: {}}

Object.defineProperty(myObject, 'test', {
    configurable: false,
    enumerable: true,
    writable: false
})

myObject.test.test = 'test'

console.log(myObject)// {test: {test}: 'test'}

可以看到对象的情况下还是写进去了,难道要用递归把他全部的子属性的子属性都这样定义一遍?

不怕,官方有新的API 来支持这一个功能

Object.preventExtendsions(myObject)

该函数接受一个对象,该对象变得不可扩展

var myObject = {
    a: 2
};

Object.preventExtensions( myObject );

myObject.b = 3;
myObject.b; // undefined

var anotherObj = {test: {}}
Object.preventExtensions( anotherObj);
anotherObj.test.test = 3;
anotherObj.test.test; // 3

从这段代码,可以看到 preventExtensions 仅仅是支持 顶层对象的不可扩展,并不能限制子层对象的扩展。

seal() 封印

Object.seal(myObjct) 会把myObject 的所有属性的 configurable 都设置为false(注意writable 还是为true的),并且都不可扩展。(但是子层引用属性仍然能被修改)

freeze() 冻结

Object.freeze(myObjct) 会把myObject 的所有属性的 configurable 都设置为false writable 为false,并且都不可扩展。(但是子层引用属性仍然能被修改),这是你能在js中获得的最不可变的对象了。

访问器描述符

对象描述符,除了属性描述符以外,还有一种访问器描述符。

get

Object.defineProperty(myObject, 'test', {
    get () {
        console.log('获取值')
        return 'testsetestest'
    }
})
myObject.test = 222;
console.log(myObject.test);
// 输出
获取值
testsetestest

set

Object.defineProperty(myObject, 'test', {
    get () {
        console.log('获取值')
        return this.test2
    },
    set (val) {
        console.log('设置值')
        this.test2 = val
     }
})
myObject.test = 'ttttttt'
console.log(myObject.test2)f // ttttttt

综合上面两段代码来看,当我们给该属性设置了get,然后再给该属性赋值时,js会执行get方法 并且 把赋的值给忽略掉,如果我们要让get 和 set 按照正常的思维工作起来,就要借助另外一个私有的变量 来达到正常操作的目的。而事实上 这就是vue2.x 的响应式核心

最后

所有的特性都已介绍完毕

路漫漫其修远兮, 吾将上下而求索.


有趣的李
378 声望277 粉丝